﻿Imports System.IO
'Imports System.Xml.Serialization
Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Graphics
'Imports Microsoft.Xna.Framework.Content
Imports Microsoft.Xna.Framework.GamerServices

Public Class GameManager
    Inherits DrawableGameComponent

#Region " Fields "
    Private mScreens As New Dictionary(Of String, Screen)
    Private mSpriteBatch As SpriteBatch
    Private mFont As SpriteFont
    Private mInput As IGamePadInputManager
    Private mIsInitialized As Boolean
    Private mAudioManager As IAudioManager
    Private mSprites As New Dictionary(Of String, Texture2D)
    Private mFocusedScreen As GameScreen
    Private mNotifications As New Queue(Of NotificationMessage)
#End Region

#Region " Properties "
    ''' <summary>
    ''' A default SpriteBatch shared by all the screens. This saves
    ''' each screen having to bother creating their own local instance.
    ''' </summary>
    Public ReadOnly Property SpriteBatch() As SpriteBatch
        Get
            SpriteBatch = mSpriteBatch
        End Get
    End Property

    ''' <summary>
    ''' A default font shared by all the screens. This saves
    ''' each screen having to bother loading their own local copy.
    ''' </summary>
    Public ReadOnly Property Font() As SpriteFont
        Get
            Font = mFont
        End Get
    End Property

    ''' <summary>
    ''' In future we could possibly make each screen have its own sprites. 
    ''' For now, we are pre-loading all of our sprites
    ''' </summary>
    Public ReadOnly Property Sprites() As Dictionary(Of String, Texture2D)
        Get
            Sprites = mSprites
        End Get
    End Property

#End Region ' Properties

#Region " Initialization "
    ''' <summary>
    ''' Constructs a new screen manager component.
    ''' </summary>
    Public Sub New(ByVal game As Microsoft.Xna.Framework.Game)
        MyBase.New(game)
    End Sub

    ''' <summary>
    '''/ Initializes the screen manager component.
    ''' </summary>
    Public Overrides Sub Initialize()

        mInput = InputManagerFactory.GetInputManager

        ' Tell each of the screens to initialize.
        For Each gs As GameScreen In mScreens.Values
            gs.Initialize()
        Next
        mIsInitialized = True

        MyBase.Initialize()

    End Sub

    Protected Overrides Sub LoadContent()
        ' Check registered game services for an object that implements IAudioManager. 
        mAudioManager = Game.Services.GetService(GetType(IAudioManager))
        ' If found, use it. Else, use the default AudioManager.
        If mAudioManager Is Nothing Then
            mAudioManager = New AudioManager()
        End If

        ' Have to create in Initialize as it is called before LoadContent

        mAudioManager.LoadContent()

        ' Load content belonging to the game manager.
        mSpriteBatch = New SpriteBatch(GraphicsDevice)
        mFont = Game.Content.Load(Of SpriteFont)("ComicSansMS")
        'mSprites.Add("blank", Game.Content.Load(Of Texture2D)("blank")) ' Used for transition screen
        mSprites.Add("MessageBox", Game.Content.Load(Of Texture2D)("MessageBox"))

        ' Tell each of the screens to load their content.
        For Each screen As GameScreen In mScreens.Values
            screen.LoadContent()
        Next
    End Sub

    Protected Overrides Sub UnloadContent()

        ' Tell each of the screens to unload their content.
        For Each screen As GameScreen In mScreens.Values
            screen.UnloadContent()
        Next
    End Sub

#End Region ' Initialization

#Region " Update and Draw "

    ''' <summary>
    ''' Allows each screen to run logic.
    ''' </summary>
    Public Overrides Sub Update(ByVal gameTime As GameTime)

        ' Read the keyboard and gamepad.
        mInput.Update(GameTime.ElapsedGameTime.TotalSeconds)
        mAudioManager.Update(gameTime.ElapsedGameTime.TotalSeconds)

        'If Game.IsActive Then TODO
        mFocusedScreen.HandleInput(mInput)

        mFocusedScreen.Update(gameTime.ElapsedGameTime.TotalSeconds)

        ' If we have notifications, then call update on the first one (the active one)
        If mNotifications.Count > 0 Then mNotifications.Peek.Update(gameTime.ElapsedGameTime.TotalSeconds)


        ' After all updates, tell the screen to update its camera (if it has one)
        mFocusedScreen.UpdatePresentation(gameTime.ElapsedGameTime.TotalSeconds)
        ' End If

    End Sub

    ''' <summary>
    ''' Tells each screen to draw itself.
    ''' </summary>
    Public Overrides Sub Draw(ByVal gameTime As GameTime)
        'TODO If Game.IsActive Then
        SpriteBatch.Begin()
        mFocusedScreen.Draw(gameTime.ElapsedGameTime.TotalSeconds)

        ' If we have notifications, then call draw on the first one (the active one)
        If mNotifications.Count > 0 Then mNotifications.Peek.Draw(gameTime.ElapsedGameTime.TotalSeconds)
        SpriteBatch.End()
        'End If
    End Sub

#End Region ' Update and Draw

#Region " Audio "

    Public Sub PlaySound(ByVal friendlyName As String)
        mAudioManager.PlaySound(friendlyName)
    End Sub

    Public Property AmbientMood() As String
        Get
            AmbientMood = mAudioManager.AmbientMood
        End Get
        Set(ByVal value As String)
            mAudioManager.AmbientMood = value
        End Set
    End Property

#End Region ' Audio

#Region " Screens "

    ''' <summary>
    ''' Adds a new screen to the screen manager.
    ''' </summary>
    Public Sub AddScreen(ByVal screen As GameScreen)
        screen.GameManager = Me

        ' If we have a graphics device, tell the screen to load content.
        If mIsInitialized Then _
                screen.LoadContent()
        With mScreens
            If .ContainsKey(screen.ScreenName) Then RemoveScreen(screen)
            .Add(screen.ScreenName, screen)
        End With

        If mFocusedScreen Is Nothing Then mFocusedScreen = screen
    End Sub

    ''' <summary>
    ''' Removes a screen from the screen manager. You should normally
    ''' use GameScreen.ExitScreen instead of calling this directly, so
    ''' the screen can gradually transition off rather than just being
    ''' instantly removed.
    ''' </summary>
    Public Sub RemoveScreen(ByVal screen As GameScreen)

        ' If we have a graphics device, tell the screen to unload content.
        If mIsInitialized Then _
                screen.UnloadContent()

        mScreens.Remove(screen.ScreenName)
    End Sub

    Public Sub ChangeScreen(ByVal ScreenName As String)
        AmbientMood = mScreens(ScreenName).AmbientMood
        mFocusedScreen = New TransitionScreen(Me, mFocusedScreen, mScreens(ScreenName), AddressOf TransitionFinished)
    End Sub

    Private Sub TransitionFinished()
        Dim ts As TransitionScreen = mFocusedScreen
        mFocusedScreen = CType(mFocusedScreen, TransitionScreen).NewScreen
    End Sub

    Public Function Screens() As Dictionary(Of String, Screen)
        Screens = mScreens
    End Function

#End Region ' Screens

#Region " State Management "

    Public Sub SaveTo(ByVal Filename As String)
        Dim sw As New StreamWriter(Filename)
        ' Write out any information needed by the game manager

        ' Work through each game screen that implements the iCanSaveState, 
        ' writing out the screen name and then calling the SaveTo method of the interface.
        Dim screensToSave As New List(Of GameScreen)
        For Each gs As GameScreen In mScreens.Values
            If gs Is GetType(ICanSaveState) Then
                screensToSave.Add(gs)
            End If
        Next

        sw.WriteLine(screensToSave.Count)
        For Each gs As GameScreen In screensToSave
            sw.WriteLine(gs.ScreenName)
            CType(gs, ICanSaveState).SaveTo(sw)
        Next

    End Sub

    Public Sub LoadFrom(ByVal Filename As String)
        Dim sr As New StreamReader(Filename)
        ' Reading in the information for the game manager 

        ' Read the screen name from the file, match it to a game screen in its list 
        ' and call the LoadFrom method of the interface.
        Dim count As Integer = sr.ReadLine
        For i As Integer = 0 To count
            CType(mScreens(sr.ReadLine()), ICanSaveState).LoadFrom(sr)
        Next

    End Sub

#End Region ' State Management

#Region " Notification and Menu "

    Public Sub ShowNotification(ByVal message As String, ByVal fadeInTime As Single, ByVal onScreenTime As Single, ByVal fadeOutTime As Single, ByVal positionOnScreen As NotificationPosition)
        mNotifications.Enqueue(New NotificationMessage(Me, message, onScreenTime, positionOnScreen, AddressOf NotificationComplete, fadeInTime, fadeOutTime))
    End Sub

    Public Sub ShowNotification(ByVal message As String)
        mNotifications.Enqueue(New NotificationMessage(Me, message, AddressOf NotificationComplete))
    End Sub

    Public Sub NotificationComplete()
        mNotifications.Dequeue()
    End Sub

    Public Sub ShowMenu(ByVal menu As BasicMenu)
        ' Set the OnComplete call-back
        menu.OnComplete = AddressOf MenuCompleteCallback
        ' Change the focus to the basic menu class 
        menu.BackgroundScreen = mFocusedScreen
        mFocusedScreen = menu

    End Sub

    Public Sub MenuCompleteCallback(ByVal screen As GameScreen)
        ' Reset the focus
        mFocusedScreen = New TransitionScreen(Me, mFocusedScreen, screen, AddressOf TransitionFinished)
        mFocusedScreen = screen
    End Sub

#End Region ' Notification and Menu

    Public Sub [Exit]()
        Game.Exit()
    End Sub

End Class
